
package gamepack;

import java.applet.Applet;
import java.awt.Frame;
import java.util.Vector;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Color;
import java.util.Enumeration;
import java.awt.LayoutManager;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.IOException;

 class DefaultPainter implements Painter {
	public void paint(Graphics g,int state) {

		Dimension d = Application.getSize();
		int x = d.width/2, y = d.height/2;
		String s;	int w; int h = 45;


		switch( state ) {

			case Game.LOAD:
				Application.setBackground(new Color(128,128,128));
				g.setFont(new Font("Serif",Font.BOLD,36));
				s = "Loading";
				w = g.getFontMetrics().stringWidth(s)/2;
				g.drawString(s,x - w,y - h);
				break;

			case Game.PAUSE:
				g.setFont(new Font("Serif",Font.BOLD,36));
				s = "Pause";
				w = g.getFontMetrics().stringWidth(s)/2;
				g.drawString(s,x - w,y + 2*h);
				break;

			case Game.PLAY:
				break;

			case Game.UNLOAD:
				Application.setBackground(new Color(200,100,100));
				g.setFont(new Font("Serif",Font.BOLD,36));
				s = "UNKNOWN";
				w = g.getFontMetrics().stringWidth(s)/2;
				g.drawString(s,x - w,y + 4*h);
				break;
			
		}
	}
}

public class Game {

	/** You can pass 'null' or 'default painter to init() method. These 2 are different - 
	null means there is no painter.
	
	*/
	static Painter defaultPainter = new DefaultPainter();
	public static Painter getDefaultPainter() {return defaultPainter; }

	/** One of the possible states of the game */
	public static final int LOAD = 0;
	public static final int PLAY = 1;
	public static final int PAUSE   = 2;
	public static final int UNLOAD =-1;
    	/** The current status of the game. It can take the following values -
		UNKNOWN,LOADING,PLAYING,PAUSE;				 */
	static int state = LOAD;


	public static final int VERBOSE_PRINT	  = 1<<0;
	public static final int VERBOSE_LIFECYCLE = 1<<1;
	public static final int VERBOSE_GENERAL   = 1<<2;
	public static final int VERBOSE_ALL		  = VERBOSE_LIFECYCLE + VERBOSE_GENERAL + VERBOSE_PRINT;
	public static final int VERBOSE_NONE	  = 0;


    public static long gameClock = 0;

    private static long fps = 25;
    private static long period =  1 * 1000 * 1000000L / fps;     // in nanoseconds
    public static void setFPS(long fps) {
        Game.fps = fps;
        Game.period = 1 * 1000 * 1000000L / fps;
    }
    public static void setPeriod(long period) {
        Game.period = period;
        Game.fps =  1 * 1000 * 1000000L / Game.period;
    }
    public static long getFPS() { return Game.fps; }
    public static long getPeriod() {   return Game.period; }
    public static long getTime() { return gameClock * period; }
    public static long getRealTime() {  return System.nanoTime();    }

	public static int verbose;
	private static OutputStream   outputStream = System.out;
	private static BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(System.out));

	private static int isInitialized = 0;
	private static int UNINITIALIZED=0,HALF_INITIALIZED=-1,INITIALIZED=1;
	// 0 means not initialized
	// -1 started initializing
	//  1 means initialized

	//----------------------------------------------------------------------
	// static final int
	//----------------------------------------------------------------------

	/** Error messages for debugging only.*/
	public static String errMsg;

    // Used by gamepack engine for debugging support..
    // TODO: current implementation is not complete.
	public static String debugFlag;

	/** Reference to a painter object */
	public static Painter painter;


	//----------------------------------------------------------------------
	// static data
	//----------------------------------------------------------------------

	/**
	The Backbuffer used for animation.
	The size of the Backbuffer may not remain constant.			*/
	protected static Image backBuffer;

	/** Graphics Context of the backbuffer */
	public static Graphics gback;

	/** Graphics Context of the Application */
	public static Graphics gfront;

	/** The amount of time the World thread sleeps (in milliseconds)
	This affects the framerate of the game.
	The default value is 1000/33 millisecs

	protected static long sleepTime = 1000/40; */


	/** Reference to a painter object used by debug methods.
	@see setDebugPainter() */
	static Painter debugPainter;


	/**
	Initialises the gamepack package.
	Must be compulsorily called before using any functionality of gamepack
	It creates the framelet object which encapsulates the application.
	Before this object is created, other entities/classes/objects of
	gamepack will not work properly.
	*/

	/*public static void init(Applet a,Painter p)
	{
		if( isInitialized == UNINITIALIZED) {
			isInitialized = HALF_INITIALIZED;

			// NOTE:
			//The start() paint() method will get called after
			//this method is over. They will work as expected
			//since (isInitialized==INITIALIZED) after this method.
			
			Application.initialize(0,0,a,null);

			// Will set isInitialized = INITIALIZED & refresh screen
			init(p);		// will set isInitialized = INITIALIZED
		}
	}*/

	public static void init(int w,int h,Applet a,Frame peer,LayoutManager mgr,Painter p) {
		if( isInitialized == UNINITIALIZED) {
			isInitialized = HALF_INITIALIZED;

			/* NOTE:
			1. If paint() gets called, due to initialize() it must do nothing.
			2. if init(Applet) get called it is smart enough not to do anything.
			3. if resume() is smart enough not to do anything.

			Surprisingly only the paint() method of applet got invoked.
			init() and start() method were not invoked.
			*/
			Application.initialize(w,h,a,peer,mgr);

			// Will set isInitialized = INITIALIZED & refresh screen
			init(p);
		}
	}

// Trying to keep fewer signatures to avoid confusion, hence commenting
//	public static void init(Applet a)
//	{	init(a,null);			}

//	public static void init(int w,int h,Applet a)
//	{	init(w,h,a,null);		}


	/**	Initialises the gamepack package. Must be compulsorily
		called before using any functionality of gamepack. */

	private static void init(Painter p) {
		// state = LOADING;  	// commented for efficiency.
		//sleepTime = 1000/35;
		Dimension d = Application.getSize();

		if( (backBuffer = Application.createImage(d.width,d.height)) == null )
			throw new RuntimeException("Application.createImage() failed.");

		if( (gfront = Application.getGraphics()) == null )
			throw new RuntimeException("Application.getGraphics() failed.");

		if( (gback = backBuffer.getGraphics()) == null )
			throw new RuntimeException("backBuffer.getGraphics() failed.");

		painter = p; //(p == null) ? new DefaultPainter() : p ;
		isInitialized = INITIALIZED;

		// Now that the frame is truely initialized lets call paint()

		if( painter != null ) { // painter = null only if this Application is not a real game.
			paint( null );	// refresh application.
		}
	}

	/** This method can be used to refresh the World Application. */
	public static void paint(Graphics graphics)
	{
		// If the Framelet was a frame then this method would
		// get called during (isInitialized==HALF_INITIALIZED)
		if( isInitialized != INITIALIZED )
			return; // Lets do nothing

		if( painter == null ) { // This could be if the Application is not exactly a game but uses 
							    // gamepack for its useful Resourceloading classes. So simply call the paint of the component
			//throw new GamePackException("paint(): painter=null");
			Application.c.paint(graphics);
		} else {
			painter.paint(gfront,state);
		}
	}


	/**	This methods resumes the game,  It does not wait for the game to
	    be paused before returning. */

	public static void resume() {
		// If the Framelet was a frame then this method would
		// get called during (isInitialized==HALF_INITIALIZED)

		if( isInitialized != INITIALIZED )
			return;

		if( World.currentWorld != null && World.currentWorld.threadState == ThreadState.SUSPEND ) {
			// Both the below methods return immediately
			World.currentWorld.setThreadState(ThreadState.RUN,PLAY);
			//Sprite.resume();
		}

	}

	/** This method pauses the game. It does not wait for the game to
	    be paused before returning. */

	public static void pause() {
	Game.println(" Game.pause() invoked");
		if( World.currentWorld != null && World.currentWorld.threadState == ThreadState.RUN ) {
			Game.println(" Game.pause() about to setThreadState ");
			// Both the methods return immediately
			//Sprite.pause();
			World.currentWorld.setThreadState(ThreadState.SUSPEND,PAUSE);
		}
	}

	/** This method return after killing all gamepack related threads. */
	// This method may not be called to improve speed.
	public static void destroy() {
		Environment.killSprites();	// return after killing all sprites

		// Ensure that the World thread will die.
		if( World.currentWorld != null ) {
			switch( World.currentWorld.threadState ) {
			  case ThreadState.STOP:	break;
			  case ThreadState.SUSPEND:
			  			World.currentWorld.setThreadState(ThreadState.RUN,Game.UNLOAD);
			  			World.currentWorld.setThreadState(ThreadState.STOP,Game.UNLOAD);
                        break;
			  case ThreadState.RUN:
			  			World.currentWorld.setThreadState(ThreadState.STOP,Game.UNLOAD);
                        break;
			}
		}
	}


	/** sets the state of the World to the given "state" */
	public static void setState(int state)
	{	Game.state = state;	}

	public static int getState() { return Game.state; }
	public static boolean getState(int state) {
		return ( Game.state == state );
	}
	/** These methods will output messages to the game outputstream, which by
		default is "System.out" (console). @see setOutputStream()
		Also this method will work only if World.verbose field is set to
		"Game.VERBOSE_PRINT". By default it is always set.
	*/
	public static void println(String s) {
		if(  ((Game.verbose & Game.VERBOSE_PRINT) != 0) && bw != null ) {
			try {	bw.write(s + "\n");	bw.flush();	}
			catch( IOException e) {}
		}
	}

	public static void print(String s) {
		if( ((Game.verbose & Game.VERBOSE_PRINT) != 0) && bw != null ) {
			try {	bw.write(s); bw.flush();  }
			catch( IOException e) {}
		}
	}

	/**  The following valid parameters may be passed
	VERBOSE_PRINT	  	(Game.print() and Game.println() are enabled) <br>
	VERBOSE_LIFECYCLE 	(Sprite birth and death messages are enabled) <br>
	VERBOSE_GENERAL   	(Other general messages/warnings from gamepack are enabled) <br>
	VERBOSE_ALL		  	(All messages are enabled) <br>
	VERBOSE_NONE	  	(No messages are enabled) <br>
	(Note: Any combination of the above parameter may also be passed
	VERBOSE_LIFECYCLE & VERBOSE_GENERAL only work when they are combined with VERBOSE_PRINT
	*/
	public static void setVerbose(int verbose_attribute) {	verbose = verbose_attribute; }

	/** This methods sets the game outputstream. By default it is set to System.out */
	public static void setOutputStream(OutputStream o) {
		if( o == null ) {
			bw = null;
			outputStream = null;
		}
		else {
			bw = new BufferedWriter( new OutputStreamWriter(o) );
			outputStream = o;
		}
	}
	public static OutputStream getOutputStream() {	return outputStream;	}
 }

